from SimpleXMLRPCServer import SimpleXMLRPCServer
from SocketServer import ThreadingMixIn
import logging
import xmlrpclib
import traceback
import socket
import time
from pgdump_rsync.dump_file import BlobTocEntry

class XMLRPCSyncServer:
    """XML-RPC layer to wrap a server object"""

    class _XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
        def server_bind(self):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            SimpleXMLRPCServer.server_bind(self)


    def __init__(self, server, opts):
        self.__server = server
        self.__rpcserver = self._XMLRPCServer((opts.ip or "", opts.port), allow_none=True)
        self.__rpcserver.register_introspection_functions()
        self.__rpcserver.register_instance(self)

    def serve_forever(self):
        return self.__rpcserver.serve_forever()

    @classmethod
    def add_parse_opts(klass, parser):
        parser.add_option("-P", "--port", action="store", type="int",
                           dest="port", default=6543,
                           help="Port where server listens")
        parser.add_option("-i", "--ip", action="store", type="string",
                           dest="ip", default="",
                           help="Host or IP where server listens. Leave empty to listen on all interfaces")

    def __getattr__(self, attrName):
        if attrName in ["get_metadata", "get_entry_data", "get_size_and_hash", "get_blob_data"]:
            fn = getattr(self.__server, attrName)

            def __binaryEncode(*args, **kargs):
                return xmlrpclib.Binary(fn(*args, **kargs))
            return __binaryEncode
        else:
            return getattr(self.__server, attrName)

    def calc_delta(self, dump_id, signature):
        signature = signature.data
        return xmlrpclib.Binary(self.__server.calc_delta(dump_id, signature))

    def get_size_and_hash(self, dump_id):
        size, hash = self.__server.get_size_and_hash(dump_id)
        return size, xmlrpclib.Binary(hash)

    def get_blob_entries(self):
        ret = []
        for be in self.__server.get_blob_entries():
            ret.append({"oid": str(be.oid), "size": str(be.size),
                        "offset": str(be.offset), "hash": be.hash})
        return ret


class XMLRPCServerProxy(object):
    """Proxy for an XML-RPC Server, for use from client"""

    def __init__(self, serverURI, opts):
        self.__serverURI = serverURI
        server = xmlrpclib.ServerProxy(serverURI, allow_none=True)
        if opts.retries:
            server = _RetryProxy(server, opts.retries, opts.sleep_time)
        self.__server = server

    @classmethod
    def add_parse_opts(klass, parser):
        parser.add_option("-T", "--timeout", action="store", type="int",
                           dest="timeout", default="1200",
                           help="Socket timeout (0: disable, default: 1200)")
        parser.add_option("-R", "--maxretries", action="store", type="int",
                           dest="retries", default="10",
                           help="Max retries in case of socket errors")
        parser.add_option("-S", "--error-sleep", action="store", type="int",
                           dest="sleep_time", default="30",
                           help="Seconds to sleep in case of socket errors (default: 30)")

    def __getattr__(self, attrName):
        if attrName in ["get_metadata", "get_entry_data", "get_size_and_hash", "get_blob_data"]:

            def __binaryDecode(*args, **kargs):
                method = getattr(self.__server, attrName)
                return method(*args, **kargs).data
            return __binaryDecode
        else:
            return getattr(self.__server, attrName)

    def calc_delta(self, dump_id, signature):
        return self.__server.calc_delta(dump_id, xmlrpclib.Binary(signature))

    def get_size_and_hash(self, dump_id):
        size, hash = self.__server.get_size_and_hash(dump_id)
        return size, hash.data

    def get_blob_entries(self):
        for be in self.__server.get_blob_entries():
            be["oid"] = int(be["oid"])
            be["size"] = int(be["size"])
            be["offset"] = int(be["offset"])
            yield BlobTocEntry(**be)


class _RetryProxy(object):
    """Wrapper to retry calls in case of socket errors"""

    def __init__(self, server, tries, sleep_time):
        self._tries = tries
        self._originalTries = tries
        self._server = server
        self._sleep_time = sleep_time

    class MethodProxy:

        def __init__(self, parent, methodName):
            self.__parent = parent
            self.__methodName = methodName

        def __call__(self, *args, **kargs):
            try:
                ret = getattr(self.__parent._server, self.__methodName)(*args, **kargs)
                self.__parent._tries = self.__parent._originalTries # Reseteo los tries
                return ret
            except socket.error, e:
                if self.__parent._tries > 0:
                    self.__parent._tries -= 1
                    logging.debug("Error: %s" % traceback.format_exc())
                    logging.warn("SocketError: %s, retrying... %d retries left. " % (str(e),
                                                                                     self.__parent._tries))
                    # Each time it fails, sleep more
                    sleep = self.__parent._sleep_time * (self.__parent._originalTries - self.__parent._tries)
                    time.sleep(sleep)
                    return self(*args, **kargs)
                else:
                    raise e

    def __getattr__(self, attName):
        if attName[:2] == "__":
            return getattr(self._server, attName)
        return self.MethodProxy(self, attName)
